/*
 * Copyright 2010-2011 Marcin Blazejewski
 *
 * This file is part of Ground0.
 *
 * Ground0 is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Ground0 is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Ground0. If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <config.h>

#include "eye.hpp"
#include "stuff.hpp"
#include "wloader.hpp"
#include "camera.hpp"
#include "world.hpp"

#include <iostream>
#include <SDL/SDL.h>
#include <GL/gl.h>
#include <GL/glu.h>
#ifdef WIN32
#include <GL/glext.h>
#endif
#include <stdlib.h>


#include <math.h>

using namespace std;

Eye::Eye(int w, int h, int _bpp, bool _fs, const char *title) :
    width(w), height(h), bpp(_bpp), fs(_fs) {

    screen = NULL;
    fontbase = 0;
    wireframe = false;
    fog = false;

    // Init SDL

    if (SDL_Init(SDL_INIT_VIDEO) < 0) 
        throw string("init SDL: ") + string(SDL_GetError());

    SDL_ShowCursor(0);

    // Configure SDL/OpenGL
    SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 8 );
    SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 8 );
    SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 8 );
    SDL_GL_SetAttribute( SDL_GL_ALPHA_SIZE, 8 );
    SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 16 );
    SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );

    if (fs)
        screen = SDL_SetVideoMode(w, h, bpp, SDL_OPENGL | SDL_FULLSCREEN);
    else
        screen = SDL_SetVideoMode(w, h, bpp, SDL_OPENGL | SDL_RESIZABLE);
    
    if (!screen)
        throw string("set video mode: ") + string(SDL_GetError());

    SDL_WM_SetCaption(title, NULL);
    
    // Depth buffer
    glClearDepth(1.0f);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    
    // OpenGL features
    glShadeModel(GL_SMOOTH);
    //glClearColor(0,0,0,0);
    glFrontFace(GL_CCW);
    glClearStencil(0);

    glEnable(GL_LIGHTING);
    glEnable(GL_TEXTURE_2D);
    glBlendFunc(GL_SRC_ALPHA,GL_ONE);

    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

    fogColor[0] = 0;
    fogColor[1] = 0;
    fogColor[2] = 0.07;
    fogColor[3] = 1;
    glFogi(GL_FOG_MODE, GL_EXP2);
    glFogfv(GL_FOG_COLOR, fogColor);
    glFogf(GL_FOG_DENSITY, 0.003f);
    glHint(GL_FOG_HINT, GL_DONT_CARE);
    glFogf(GL_FOG_START, 5.0f);
    glFogf(GL_FOG_END, 15.0f);

    if (fog) {
        clip_dist = 480;
        glEnable(GL_FOG);
        glClearColor(fogColor[0], fogColor[1], fogColor[2], fogColor[3]);
    } else {
        clip_dist = 1500;
        glDisable(GL_FOG); 
        glClearColor(0,0,0,0);
    }
    resize(width, height);

    mat_font = new Material();
    clip_dist = 800;

    cout << "[info  ] OpenGL version supported: " << (const char*)glGetString(GL_VERSION) << endl;

    marker_dl = glGenLists(1);
    glNewList(marker_dl, GL_COMPILE);
    glBegin(GL_LINES);
    float dd = .4;
    glVertex3f(-dd, 0, 0); glVertex3f(0, dd, 0);  
    glVertex3f(0, dd, 0); glVertex3f(dd, 0, 0); 
    glVertex3f(dd, 0, 0); glVertex3f(0, -dd, 0);
    glVertex3f(0, -dd, 0); glVertex3f(-dd, 0, 0);
    glEnd();
    glEndList();
}

Eye::~Eye() {
    if (mat_font) delete mat_font;

    if (fontbase) glDeleteLists(fontbase, 256);
    SDL_Quit();
}

void Eye::resize(int w, int h) {
    if (h==0) h = 1;

    cout << "[debug ] resize to " << w << ", " << h << endl;

    if (fs)
        screen = SDL_SetVideoMode(w, h, bpp, SDL_OPENGL | SDL_FULLSCREEN);
    else
        screen = SDL_SetVideoMode(w, h, bpp, SDL_OPENGL | SDL_RESIZABLE);
    
    // Setup window
    glViewport(0,0, w, h);
    width = w;
    height = h;

    // Recalculate projection matrix: 45 deg view angle
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45.0f, (GLfloat)w/(GLfloat)h, 0.1, clip_dist);
    //glOrtho(-5.0f, 5.0, -5.0, 5.0, 0.1, 600.0);

    // reset modelview matrix
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

void Eye::set_fov(float fov) {
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(fov, (GLfloat)width/(GLfloat)height, 0.1, clip_dist);

    // reset modelview matrix
    glMatrixMode(GL_MODELVIEW);
}

void Eye::toggle_fullscreen() {
    fs = !fs;
    SDL_WM_ToggleFullScreen(screen);
}

void Eye::load_font(const char *fname) {
    int i;
    float x, y;
    int fch_size = 16;
    float fch_nsize = 1.0f / 16.0f;
    
    mat_font->set_texture(fname);
    mat_font->diffuse[0] = 1.0;
    mat_font->diffuse[1] = 1.0;
    mat_font->diffuse[2] = 1.0;

    fontbase = glGenLists(256);
    mat_font->apply();

    for (i = 0; i < 256; i++) {
        x = (i % 16) / 16.0f;
        y = (i / 16) / 16.0f;

        glNewList(fontbase + i, GL_COMPILE);
        glBegin(GL_QUADS);
        
        // bottom left
        glTexCoord2f(x, 1.0 - y - fch_nsize);
        glVertex2i(0, 0);

        // bottom right
        glTexCoord2f(x + fch_nsize, 1.0 - y - fch_nsize);
        glVertex2i(fch_size, 0);

        // top right
        glTexCoord2f(x + fch_nsize, 1.0 - y);
        glVertex2i(fch_size, fch_size);

        // top left
        glTexCoord2f(x, 1.0 - y);
        glVertex2i(0, fch_size);

        glEnd();
        glTranslated(fch_size, 0, 0);
        glEndList();
    }
}

void Eye::print(int x, int y, const char *s) {
    
    glBindTexture(GL_TEXTURE_2D, mat_font->texture);

    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    glOrtho(0, width, 0, height, -1, 1);

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
    glTranslated(x, y, 0);
    //glTranslated(0, 100, 0);

    glListBase(fontbase-32);
    glCallLists(strlen(s), GL_UNSIGNED_BYTE, s);

    glPopMatrix();

    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
}

/*******************************************************************************/
/* Basic Scene */
/*******************************************************************************/

BasicScene::BasicScene() {
}

void BasicScene::set_camera(Camera *cam) {
    if (camera) delete camera;
    camera = cam;
}

void BasicScene::relight() {
    int i;

    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient.color);

    for (i = 0; i < 8; i++) {
        if (lights[i].on) {
            glLightfv(GL_LIGHT0 + i, GL_DIFFUSE, lights[i].color);
            glEnable(GL_LIGHT0 + i);
        } else {
            glDisable(GL_LIGHT0 + i);
        }
    }
}

void BasicScene::render_light() {
    for (int l = 0; l < 8; l++) {
        if (lights[l].on) {
            glLightfv(GL_LIGHT0 + l, GL_POSITION, lights[l].pos);
        }
    }
}
